# -*- coding: utf-8 -*-
# @Time    : 2017/4/29

from itertools import chain
from sympy import S, simplify, Union
from collections import defaultdict
from mathsolver.functions.base import (new_latex, BaseValue, BaseFunction, BasePoint, BasePoints, BaseVariable, BaseEq,
                                       BaseIneq)
from mathsolver.functions.budengshi.jiebudengshi import JieBuDengShi
from mathsolver.functions.daoshu.daoshuyunsuan import HanShuDaoShuYunSuan
from mathsolver.functions.hanshu.base import is_piecefunc
from mathsolver.functions.hanshu.dingyiyu import QiuDingYiYu
from mathsolver.functions.hanshu.helper import check_func, check_inter, process_m_eqsolver_output, m_simplify
from mathsolver.functions.mathematica.mathematicaSolve import MathematicaSolve


class HanShuJiZhi(BaseFunction):
    """利用导数求函数的极值
    
    完备的输出 result:
        1. 不含参数：[[jizhi_x, jizhi_y, is_jidazhi], ...]
        2. 含参数：{canshu_range: [[jizhi_x, jizhi_y, is_jidazhi], [*], ...], ... } 
    
    jixiaozhidian, jidazhidian, jizhidian:
        1. 不含参数：[[jizhi_x, jizhi_y], ...]
        2. 含参数：{canshu_range: [[jizhi_x, jizhi_y], [*], ...], ... }
    
    Output[0]: 
        1. 不含参数：BasePoint, BasePoints 类型
        2. 含参数：baseValue({}) 类型 """
    result = None
    jizhijizhidiandian = None
    jixiaozhidian = None
    jidazhidian = None
    canshu = None
    var = None
    zero_jizhi_interval = None

    def solver(self, *args):
        func = check_func(args[0])
        expr, var, dingyiyu, has_canshu, canshu_symbol, canshu_interval = self.process_input(args)
        self.label.add("利用导数求函数的极值")

        # 求函数导数
        deriv_func = HanShuDaoShuYunSuan().solver(func).output[0]
        self.steps.append(["", "求导得 {}".format(deriv_func.printing())])

        # 计算 f' = 0 在 区间上的解

        # 参数和变量的范围
        constrain_dict = {deriv_func.var: dingyiyu}
        if has_canshu:
            constrain_dict.update({canshu_symbol: canshu_interval})

        try:
            deriv_solver_symbolvalue = MathematicaSolve().solver(BaseEq([deriv_func.expression, 0]),
                                                                 BaseVariable([deriv_func.var]),
                                                                 constrain_dict).output[0]
            deriv_solver = deriv_solver_symbolvalue.value
            # 如果 Mathematic 解不出，当做无解，
            if deriv_solver and list(deriv_solver.values())[0]:
                has_solution = True
            else:
                has_solution = False
        except Exception:
            has_solution = False

        # 如果 f'(x) = 0 无解，则极值点不存在
        if has_solution:
            # 补充：对于像 {(x, a): {(2, (1, oo)), (2*a, (1, oo))}} 格式的结果，合并 2, 2*a
            deriv_solver = process_m_eqsolver_output(deriv_solver, deriv_func.var)
            self.steps.append(["", "导函数等于零的解集为 %s，即函数的极值点" % deriv_solver_symbolvalue.printing()])
        else:
            self.steps.append(["f'(x)在定义域上符号不变，函数极值点不存在", ''])
            self.output.append(BaseValue(None))
            self.jixiaozhidian = []
            self.jidazhidian = []
            self.jizhidian = []
            return self

        # 求二次导数
        d2_expr = HanShuDaoShuYunSuan().solver(deriv_func).output[0].expression

        # 2. 计算极大极小值

        self.steps.append(['', "根据二次导数 f''(x) 的符号可知"])

        # 如果不含参数
        if not has_canshu:
            result = []  # 极值点
            jizhi_xs = deriv_solver[var]
            for jizhi_x in jizhi_xs:
                d2_jizhi = d2_expr.subs(var, jizhi_x)
                if d2_jizhi != 0:
                    result.append([jizhi_x, simplify(expr.subs(var, jizhi_x)), bool(d2_jizhi < 0)])  # 二次导数小于0，极大值点
        # 如果含参数
        else:
            result = defaultdict(list)
            # {m: {0}, (m, x): {((-oo, 0), -2), ((0, oo), -2)}}
            for prmt, vlus in deriv_solver.items():
                # (m, x): {((-oo, 0), -2), ((0, oo), -2)} 格式
                if isinstance(prmt, tuple):
                    for vlu in vlus:
                        parmt_vlu = dict(zip(prmt, vlu))
                        canshu_range = parmt_vlu[canshu_symbol]  # 区间 / 值
                        var_x_old = parmt_vlu[var]
                        # 补充：var_x 可能是 set 类型
                        if not isinstance(var_x_old, set):
                            var_x_old = [var_x_old]
                        for var_x in var_x_old:
                            f_var_x = m_simplify(expr.subs(var, var_x))  # sympy 的simplify 会出问题
                            d2_var = d2_expr.subs(var, var_x)
                            # 如果 d2_var > 0 => 极小值点
                            canshu_range_1 = \
                                JieBuDengShi().solver(BaseIneq([d2_var, '>', 0]), BaseVariable(canshu_symbol),
                                                      {canshu_symbol: canshu_range}).output[0].value.get(
                                    canshu_symbol)
                            if canshu_range_1:
                                result[canshu_range_1].append([var_x, f_var_x, False])

                            # 如果 d2_var < 0 => 极大值点
                            canshu_range_2 = \
                                JieBuDengShi().solver(BaseIneq([d2_var, '<', 0]), BaseVariable(canshu_symbol),
                                                      {canshu_symbol: canshu_range}).output[0].value.get(
                                    canshu_symbol)
                            if canshu_range_2:
                                result[canshu_range_2].append([var_x, f_var_x, True])

                # x: {x0, x1, ...}
                elif prmt != canshu_symbol:
                    for vlu in vlus:
                        f_vlu = simplify(expr.subs(var, vlu))
                        # 如果 d2_var > 0 => 极小值点
                        canshu_range_1 = JieBuDengShi().solver(BaseIneq([vlu, '>', 0]),
                                                               BaseVariable(canshu_symbol)).output[0].value.get(
                            canshu_symbol)
                        if canshu_range_1:
                            result[canshu_range_1].append([vlu, f_vlu, False])

                        # 如果 d2_var < 0 => 极大值点
                        canshu_range_2 = JieBuDengShi().solver(BaseIneq([vlu, '<', 0]),
                                                               BaseVariable(canshu_symbol)).output[0].value.get(
                            canshu_symbol)
                        if canshu_range_2:
                            result[canshu_range_2].append([vlu, f_vlu, True])

                else:
                    pass

        # 3 输出步骤
        # 3.1 不含参数
        if isinstance(result, list):
            jidazhidian_result = []
            jixiaozhidian_result = []
            jizhidian_result = []
            for jizhi_x, jizhi_y, is_jidazhi in result:
                self.steps.append(["", "函数在 x=%s 处取到%s %s" % (
                    new_latex(jizhi_x), "极大值" if is_jidazhi else "极小值", new_latex(jizhi_y))])
                # add result to jidazhidian property
                jizhidian_result.append([jizhi_x, jizhi_y])
                if is_jidazhi:
                    jidazhidian_result.append([jizhi_x, jizhi_y])
                else:
                    jixiaozhidian_result.append([jizhi_x, jizhi_y])

            # output 输出，BasePoint（霄然使用）
            if len(result) == 1:
                self.output.append(BasePoint({"name": "", "value": jizhidian_result[0]}))
            else:
                self.output.append(BasePoints([{"name": "", "value": pt} for pt in jizhidian_result]))

        # 3.2 含参数
        else:
            canshu_considered = []
            jizhidian_result = defaultdict(list)
            jidazhidian_result = defaultdict(list)
            jixiaozhidian_result = defaultdict(list)
            # result 格式：{canshu_range: [[jizhi_x, jizhi_y, is_jidazhi], [*], ...], ... }
            for canshu_range, jizhis in result.items():
                collector = "当 %s ∈ %s 时，" % (new_latex(canshu_symbol), new_latex(canshu_range))

                for jizhi_x, jizhi_y, is_jidazhi in jizhis:
                    canshu_considered.append(canshu_range)
                    collector += "函数在 x=%s 处取到%s %s，" % (new_latex(jizhi_x),
                                                         "极大值" if is_jidazhi else "极小值", new_latex(jizhi_y))
                    # add result to jidazhidian property
                    jizhidian_result[canshu_range].append([jizhi_x, jizhi_y])
                    if is_jidazhi:
                        jidazhidian_result[canshu_range].append([jizhi_x, jizhi_y])
                    else:
                        jixiaozhidian_result[canshu_range].append([jizhi_x, jizhi_y])
                self.steps.append(["", collector])

            canshu_interval_compl = Union(*canshu_considered).complement(canshu_interval)
            if canshu_interval_compl:
                self.zero_jizhi_interval = canshu_interval_compl
                self.steps.append(["", "当 %s ∈ %s 时，函数极值不存在" % (new_latex(canshu_symbol), new_latex(canshu_interval_compl))])
            # output 输出，baseValue
            self.output.append(BaseValue(result))

        self.result = result
        self.jixiaozhidian = jixiaozhidian_result
        self.jidazhidian = jidazhidian_result
        self.jizhidian = jizhidian_result
        # print result

        return self

    def process_input(self, args):
        """处理输入"""
        func = check_func(args[0])
        expr, var = func.expression, func.var
        self.var = var
        if len(args) == 2:  # 如果给定区间，求在区间上的最值
            dingyi_interval = check_inter(args[1])
        elif func.dingyiyu:  # 区间可能放在 BaseFunc 的 dingyiyu 属性里面
            dingyi_interval = check_inter(func.dingyiyu)
        else:  # 否则求函数定义域，求在定义域上的最值
            dingyi_interval = self.search(var)
            if not dingyi_interval:
                dingyi_interval = QiuDingYiYu().solver(func)

        # 考察表达式含不含参数
        if is_piecefunc(func):
            canshu_symbol = list(set(chain(*[expr_1.free_symbols for expr_1, _ in expr])) - {var})
        else:
            canshu_symbol = list(expr.free_symbols - {var})

        # 如果含有参数，search 参数范围，如果 search 不到，默认参数属于 R
        has_canshu = False
        canshu_interval = S.Reals
        if canshu_symbol:
            canshu_symbol.sort(key=lambda arg: str(arg))
            canshu_symbol = canshu_symbol[0]
            self.canshu = canshu_symbol
            has_canshu = True
            search_canshu_interval = self.search(canshu_symbol)
            if search_canshu_interval:
                canshu_interval = search_canshu_interval

        return expr, var, dingyi_interval, has_canshu, canshu_symbol, canshu_interval


class HanShuJiXiaoZhi(HanShuJiZhi):
    """利用导数求函数的极小值
    
    Output[0]: 
        1. 不含参数：BasePoint, BasePoints 类型
        2. 含参数：baseValue({}) 类型
    """

    def solver(self, *args):
        super(HanShuJiXiaoZhi, self).solver(*args)
        result = self.jixiaozhidian
        if isinstance(result, list):
            if len(result) == 1:
                self.output = [(BasePoint({"name": "", "value": result[0]}))]
            else:
                self.output = [(BasePoints([{"name": "", "value": pt} for pt in result]))]
        else:
            self.output = [BaseValue(result)]

        return self


class HanShuJiDaZhi(HanShuJiZhi):
    """利用导数求函数的极大值"""

    def solver(self, *args):
        super(HanShuJiDaZhi, self).solver(*args)
        result = self.jidazhidian
        if isinstance(result, list):
            if len(result) == 1:
                self.output = [(BasePoint({"name": "", "value": result[0]}))]
            else:
                self.output = [(BasePoints([{"name": "", "value": pt} for pt in result]))]
        else:
            self.output = [BaseValue(result)]

        return self


if __name__ == "__main__":
    pass
